// -*- c-basic-offset: 2 default-tab-width: 2 indent-tabs-mode: t -*-
// vim: autoindent tabstop=2 noexpandtab shiftwidth=2 softtabstop=2
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "util.h"
#include "Evaluator.h"
#include "Reactor.h"
#include "Debugger.h"
#include "RModule.h"
#include "Environment.h"
#include "RCompiler.h"
#include "RJITCompiler.h"
#include "Stack.h"
#include <llvm/Analysis/Verifier.h>

#include <llvm/ExecutionEngine/GenericValue.h>

#undef rprintf
#define rprintf(...) recho("debug",    __VA_ARGS__)

#undef XWORD
#define XWORD "%2"PRIx64

using namespace R;

#define PROMPT_SIZE 0x1000
namespace {
	int is_valid_deref(word_t addr){
		return addr && !(addr % 4);
	}

	int is_valid_deref(Any* addr){
		return is_valid_deref((word_t) addr);
	}

	int isInstruction(char *instr){
		int iid = BC_LAST - 1;
		while (iid >= 0 && strcasecmp(instr, BC_names[iid]))
			iid --;
		return iid;
	}

	char *promptCommand(){
		static char buffer[PROMPT_SIZE] = {};
		char *str = NULL;
		printf("debug> ");
		while(!str){
			str = fgets(buffer, PROMPT_SIZE, stdin);
			while(str && *str && !isgraph(*str))
				str ++;
		}
		str[strlen(str) - 1] = 0; // FIXME Not a robust way to skip the newline
		return str;
	}
}

char *ltrim(char *cmd){
	while (isspace(*(cmd))) { cmd++; }
	return cmd;
}

void Debugger::dumpTopStack(Stack *stack, bool deref, int n){
	R_VAR(Any*, val);

	if(n == 0)
		n = 3;

	if(n > stack->anyStack.size())
		n = stack->anyStack.size();

	for(int i=0; i<n; i++){
		val = stack->anyStack.peek(i);
		if(deref && is_valid_deref(val)){
			printf("[0x%.4x] ptr %p ====> ", i, val);
			val->print();
		} else
			printf("[0x%.4x]\tptr: %p\t\tint: " WORD, i, val, (word_t)val);
		PrintNewLine();
	}
}

int Debugger::disassemble(int *cs, int startAt){
	int instr = startAt;
	int nbarg;
	unsigned long bc = cs[instr];
	if(bc <= BC_LAST){
		printf("%.4x: %-10s", instr, BC_names[bc]);
		nbarg = BC_args[bc];
		while(nbarg){
			bc = cs[++instr];
			switch(nbarg & BCPMASK){
				case IMM:
					printf("\t#0x%lx", bc);
					break;
				case IFI:
					printf("\t[%lx]", bc);
					break;
				case LBL:
					printf("\t:0x%lx", bc);
					break;
				case IOL:
				case IDX:
					printf("\t!0x%lx", bc);
					break;
			}
			nbarg >>= BCPLEN;
		}
		PrintNewLine();
	} else
		printf("%.4x: ERROR %lx\n", instr, bc);
	return instr - startAt;
}

void Debugger::disassemble(int *cs, int startAt, int stopAt){
	for(int instr = startAt; instr <= stopAt; instr ++)
		instr += disassemble(cs, instr);
}

// Map containing debugger commands and the member to call for executing them.
// Method must be a Debugger member (see Debugger::callCommand)
//
class DebugFunctionMap {
	friend class Debugger;

	static CVector<DebugFunctionMap> debugFunctionMap;

	bool (Debugger::*func)(char*);  // function pointer
	const char* name;
	const char* help1;
	const char* help2;
};

CVector<DebugFunctionMap> DebugFunctionMap::debugFunctionMap;

// Initialize Debug functions map.
// WARNING: If an entry is added, don't forget to set correct map size.
//
void Debugger::initMap() {

#define SET_MAP_ENTRY(LABEL, DBG_FUNC, DBG_HELP1, DBG_HELP2)						\
	{																																			\
		DebugFunctionMap& room = DebugFunctionMap::debugFunctionMap.pushRoom(); \
		room.name = LABEL;																									\
		room.func = DBG_FUNC;																								\
		room.help1 = DBG_HELP1;																							\
		room.help2 = DBG_HELP2;																							\
	}

	SET_MAP_ENTRY(".builtins", &Debugger::cmdBuiltIns, "list all Built-in's descriptions", 0);
	SET_MAP_ENTRY(".builtin", &Debugger::cmdBuiltIn, "give the given Built-in's description", "<number|name>");
	SET_MAP_ENTRY(".help", &Debugger::cmdHelp, "print this help", 0);
	SET_MAP_ENTRY(".compile", &Debugger::cmdCompile, "compile the function <number> (see map)", "<number>");
	SET_MAP_ENTRY(".watch", &Debugger::cmdWatch, "Watch a variable", "<symbol>");
	SET_MAP_ENTRY(".map", &Debugger::cmdPrintMap, "print a map of the code segment", 0);
	SET_MAP_ENTRY(".quit", &Debugger::cmdExit, "Abort program", 0);
	SET_MAP_ENTRY(".list", &Debugger::cmdListNum, "list n program instruction", "<number>");
	SET_MAP_ENTRY(".h", &Debugger::cmdHelp, "print this help", 0);
	SET_MAP_ENTRY(".b", &Debugger::cmdBreakpoint, "toggle a breakpoint", "<hex-number|instr>");
	SET_MAP_ENTRY(".l", &Debugger::cmdListNum, "list n program instruction", "<number>");
	SET_MAP_ENTRY(".n", &Debugger::cmdNext, "Next instruction", 0);
	SET_MAP_ENTRY(".w", &Debugger::cmdWatch, "Watch a variable", 0);
	SET_MAP_ENTRY(".q", &Debugger::cmdExit, "Abort program", 0);
	SET_MAP_ENTRY(".", &Debugger::cmdExitDebbuger, "Resume program", 0);

#undef SET_MAP_ENTRY
}

int Debugger::isFunction(char** cmd) {
	unsigned size = DebugFunctionMap::debugFunctionMap.size();
	int idx;
	int cmd_size=0;

	*cmd = ltrim(*cmd);

	for (idx = 0; idx<size; idx++) {
		cmd_size = strlen(DebugFunctionMap::debugFunctionMap.get(idx).name);
		if(!strncmp(*cmd, DebugFunctionMap::debugFunctionMap.get(idx).name, cmd_size)) {
			*cmd += cmd_size;
			return idx;
		}
	}
	return -1;
}

void Debugger::prompt(){
	char *cmd = NULL;
	int idx = -1;
	R_VAR(Any*, tmp);

	activate();

	showWatches();
	printStatusLine("[debug]");

	bool overrideReturn = true;
	bool cont = true;
	bool defaultActionNext = checkTrap(cs[pc], ~1);
	clearTrap(1); // Reset the trap for next

	do{
		cmd = promptCommand(), ".";
		if(overrideReturn && (overrideReturn = !strcmp(cmd, ""))){
			if(defaultActionNext)
				cmd = (char*) ".n";
			else
				cmd = (char*) ".";
		}

		if((idx = isFunction(&cmd)) < 0) {
			callExpression(cmd);
		} else {
			cont = callCommand(idx, cmd);
		}
	}while(cont);
}

bool Debugger::callCommand(int idx, char* cmd) {
	return (*this.*(DebugFunctionMap::debugFunctionMap.get(idx).func)) (ltrim(cmd));
}


Debugger::Debugger(Evaluator *e){
	initMap();
	eval = e;
	int bpointsize = eval->module->codeSize / BPOINTSBYWORD + 1; // +1 to be sure, however we can fix the computation
	breakpoints = new word_t[bpointsize];
	memset(breakpoints, 0, bpointsize * BPOINTSBYWORD);
	watches = new CVector<Symbol*>();
}

void Debugger::activate(){
	cs = eval->getCS();
	pc = eval->getPC() - 1;
	if(pc < 0) pc = 0; // FIX pc to use dbg before the true start of the machine
	stack = eval->getStack();
//	env = eval->env;
	envInfo = eval->envInfo;
}


void Debugger::printStatusLine(const char *msg){
	int pc = eval->getPC() - 1;
	if(pc < 0) pc = 0;
	fprintf(stderr, "%-15s ["XWORD" "XWORD" "XWORD"] ", msg, eval->getStack()->anyStack.size(), eval->getStack()->intStack.size(), eval->getStack()->callStack.size());
	disassemble(eval->getCS(), pc);
}

void Debugger::toggleBreakInstr(char* instr){
	toggleBreakInstr(isInstruction(instr));
}

void Debugger::toggleBreakInstr(int iid){
	if(iid < 0){
		warning("Can't toggle breakpoint on non existing instruction: '%s'", BC_names[iid]);
	} else {
		breakInstr[iid] = !breakInstr[iid];
		rprintf("Breakpoint on '%s' is %s", BC_names[iid], breakInstr[iid] ? "On" : "Off");
	}
}

void Debugger::setBreakpoint(word_t pc, word_t status){
	if(status)
		breakpoints[pc / BPOINTSBYWORD] |= (1 << (pc % BPOINTSBYWORD));
	else
		breakpoints[pc / BPOINTSBYWORD] &= ~(1 << (pc % BPOINTSBYWORD));
}

void Debugger::toggleBreakpoint(int pc){
	int set = !isBreakpoint(pc);
	setBreakpoint(pc, set);
	rprintf("Breakpoint at '0x%x' is %s", pc, set ? "On" : "Off");
}

void Debugger::listBreakPoints(){
	rprintf("Instructions breakpoints:");
	for(int i = 0; i < BC_LAST; i++)
		if(breakInstr[i])
			printf("\t%-10s", BC_names[i]);
	PrintNewLine();
	rprintf("Address breakpoints:");
	printf("\t<TODO>\n");
}

bool Debugger::checkTrap(int bc, int val){
	return (breakInstr[bc] & val) >> 1 != 0;
}
void Debugger::clearTrap(int val){
	for(int i = 0; i < BC_LAST; i++)
		breakInstr[i] &= val;
}
void Debugger::setTrap(int val){
	for(int i = 0; i < BC_LAST; i++)
		breakInstr[i] |= ~val;
}

/*
 * Command functions
 */

bool Debugger::cmdHelp(char* cmd) {
#define PRINT_HELP(c, t)\
		rprintf("%-20s\t%s", c, t)

	for(uint32_t i=0; i<DebugFunctionMap::debugFunctionMap.size(); i++) {
		if(DebugFunctionMap::debugFunctionMap.get(i).help2) {
			char buf[256]; 
			sprintf(buf, "%s %s", DebugFunctionMap::debugFunctionMap.get(i).name, DebugFunctionMap::debugFunctionMap.get(i).help2);
			PRINT_HELP(buf, DebugFunctionMap::debugFunctionMap.get(i).help1);
		} else
			PRINT_HELP(DebugFunctionMap::debugFunctionMap.get(i).name, DebugFunctionMap::debugFunctionMap.get(i).help1);
	}

	PRINT_HELP("?", "List environment");
	PRINT_HELP("address","dereference result");
	PRINT_HELP("&<address>","reference result");
	PRINT_HELP("<name>","Lookup symbol");
	PRINT_HELP("?<name>","Lookup symbol (protect names)");
	PRINT_HELP("<hex-number>","Use number as address");
	PRINT_HELP("!<hex-number>","Use constant pool entry");
	PRINT_HELP("$<number>","Stack Any (R Objects) item (negative number are allowed)");
	PRINT_HELP("$$<number>","n first Any stack item (negative number are allowed)");
	PRINT_HELP("~<number>","Stack int item (negative number are allowed)");
	PRINT_HELP("~~<number>","n first stack int item (negative number are allowed)");
	PRINT_HELP("#<number>","Stack call item (negative number are allowed)");
	PRINT_HELP("##<number>","n first stack call item (negative number are allowed)");
	PRINT_HELP("%<number>","Stack env item (negative number are allowed)");
	PRINT_HELP("%%<number>","n first stack env item (negative number are allowed)");
	PRINT_HELP(":","dump the environment");

#undef PRINT_HELP

	return true;
}

// TODO accept a start address
bool Debugger::cmdBreakpoint(char* cmd) {
	if(cmd[0] == 0){
		listBreakPoints();
		return true;
	}

	long bpoint = isInstruction(cmd);
	if(bpoint < 0){
		bpoint = strtol(cmd, NULL, 16);
		if(breakpoints)
			toggleBreakpoint(bpoint);
		else
			warning("Debbuger has not be initialized, no support for location breakpoints !");
	} else {
		toggleBreakInstr(bpoint);
	}
	return true;
}

bool Debugger::cmdListNum(char* cmd) {
	long nb = strtol(cmd, NULL, 0);
	if(eval->module->nbFuns < nb)
		nb = 0;
	
	disassemble(cs, eval->module->funInfos[nb].start, eval->module->funInfos[nb].end);
	return true;
}

bool Debugger::cmdExitDebbuger(char* cmd) {
	return false;
}

bool Debugger::cmdExit(char* cmd) {
	rprintf("Program stopped by the user!");
	abort();
	return false; // For the typechecker
}

// TODO accept a start address
bool Debugger::cmdNext(char* cmd) {
	setTrap(1);
	return false;
}

bool Debugger::cmdCompile(char* cmd) {
	uint32_t fct;
	sscanf(cmd, "%d", &fct);

	RJITCompiler* jit = static_cast<RJITCompiler*>(eval->vm()->compiler);

	llvm::Function* func = eval->vm()->compiler->llvmFunction(eval->module, fct);
	rprintf("function is at: %p", jit->ee->getPointerToFunction(func));
	rprintf("---------- verifying module consistency -------");
	llvm::verifyModule(*jit->selfModule, llvm::PrintMessageAction);
	rprintf("----------    end module verification  --------");
	return true;
}

bool Debugger::cmdPrintMap(char* cmd) {
	if(!*cmd){
		printf("------- function map: \n");
		for(uint32_t i=0; i<eval->module->nbFuns; i++){
			eval->module->funInfos[i].print();
			PrintNewLine();
		}
		printf("---------------------\n");
	} else {
		eval->module->funInfos[strtol(cmd, NULL, 16)].print();
		PrintNewLine();
	}
	return true;
}

bool Debugger::cmdWatch(char* cmd) {
	R_VAL(Symbol*, sym, SymbolTable::lookup(strlen(cmd), cmd));
	if(watches->has(sym) == -1)
		watches->push(sym);
	return true;
}

void Debugger::showWatches(){
	R_VAR(Any *, tmp);

	int len = watches->size();
	for(int i = 0; i < len; i++){
		bool has=0;
		tmp = envInfo->getVar(watches->get(i), &has);
		if(has) {
			printf("    watchpoint[%d]: ", i);
			tmp->print();
			PrintNewLine();
		}
	}
}

void Debugger::displayBuiltInEntry(BuiltInMap* bim, int id){
	if(id < 0)
		id = BuiltInMap::getEntryID(bim->name);
	if(id)
		rprintf("[%s]\tID: %2.2x\tName: '%s'\t params: %d\tfunction@: %p", bim->isSpecial ? "Special" : "Built-In", id, bim->name, bim->nbParams, bim->builtin);
	else
		rprintf("[Built-In]\tInvalid builtin");
}

bool Debugger::cmdBuiltIn(char* cmd) {
	BuiltInMap* bim = NULL;
	int len_cmd=0;

	long nb = strtol(cmd, NULL, 0);

	if (((nb >= BuiltInMap::maplen) ? 0 : nb) > 0) // Built-in ID is correct
		bim = BuiltInMap::getEntry(nb);
	else 
		bim = BuiltInMap::getEntry(cmd);

	displayBuiltInEntry(bim);
	return true;
}

bool Debugger::cmdBuiltIns(char* cmd) {
	BuiltInMap* bim = NULL;
	for(int i=1; i<BuiltInMap::maplen; i++){
		bim = BuiltInMap::getEntry(i);
		displayBuiltInEntry(bim, i);
	}
	return true;
}

void Debugger::dumpTopIntStack(Stack* stack, int n) {
	if(n == 0)
		n = 3;

	if(n > stack->intStack.size())
		n = stack->intStack.size();

	for(int i = 0; i<n; i++){
		int val = stack->intStack.peek(i);
		printf("[intStack] [0x%.4x]\tvalue: 0x%x", i, val);
		PrintNewLine();
	}
}

void Debugger::dumpTopEnvStack(Stack* stack, int n) {
	if(n == 0)
		n = 3;

	if(n > stack->envStack.size())
		n = stack->envStack.size();

	for(int i = 0; i<n; i++){
		printf("[EnvStack] [0x%.4x]\t", i);
//		stack->envStack.print(i);
		PrintNewLine();
	}
}

void Debugger::dumpTopCallStack(Stack* stack, int n) {
	if(n == 0)
		n = 3;

	if(n > stack->callStack.size())
		n = stack->callStack.size();

	for(int i = 0; i<n; i++){
		ActivationRecord* rec = stack->callStack.peek(i);
		printf("[CallStack] [0x%.4x]\tModule ID: "WORD"\tEnvironment(Envinfo): %p\tpc: "WORD"", i, (rec->module)?rec->module->moduleId:-1, rec->envInfo, rec->pc);
		PrintNewLine();
	}
}


void Debugger::callExpression(char* cmd) {
	bool deref = true;
	R_VAR(Any*, addr);
	int addr_int = NULL;
	bool isInt=false;

	if(cmd[0] == '&'){ // Activate dereference
		deref = false;
		cmd = ltrim(cmd);
	}

	if(cmd[0] == '?'){
		if(cmd[1] == 0){ // the other case is to allow protecting symbols starting with '.'
			envInfo->inspect();
			return;
		}
		cmd ++;
	}

	if(cmd[0] == '$'){ // We are speaking about the stack
		if(cmd[1] == '$'){
			dumpTopStack(stack, deref, strtol(cmd+2, NULL, 0));
			return;
		}
		long offset = strtol(++cmd, NULL, 0);
		addr = stack->anyStack.peek(offset);
	}
	else if(cmd[0] == '~') {
		if(cmd[1] == '~'){
			dumpTopIntStack(stack, strtol(cmd+2, NULL, 0));
			return;
		}
		isInt = true;
		deref = false;
		long offset = strtol(++cmd, NULL, 0);
		addr_int = stack->intStack.peek(offset);
	}
	else if(cmd[0] == '#') {
		if(cmd[1] == '#'){
			dumpTopCallStack(stack, strtol(cmd+2, NULL, 0));
			return;
		}
	}
	else if(cmd[0] == '%') {
		if(cmd[1] == '%'){
			dumpTopEnvStack(stack, strtol(cmd+2, NULL, 0));
			return;
		}
	}
	else if(cmd[0] == '!') { // We are speaking about the cpool
		addr = eval->module->cpool[strtol(++cmd, NULL, 16)];
	}
	else if(isdigit(cmd[0])){
		addr = static_cast<Any*>((void*)strtol(cmd, NULL, 16));
	} else {
		bool has = 0;
		Symbol*  sym = SymbolTable::lookup(strlen(cmd), cmd);
		addr = envInfo->getVar(sym, &has);
	}
	if(deref) {
		if(addr) { // Any* case
			if(is_valid_deref(addr)) {
				addr->print();
				PrintNewLine();
			} else {
				warning("Attempt to dereference invalid address: %p", addr);
			}
		} // end Any* case
	} else {
		if(addr)
			rprintf("[AnyStack] -> int: " WORD "\t\tptr: %p", (word_t)addr, addr);
		else if (isInt)
			rprintf("[intStack] -> int: 0x%x", addr_int);
		PrintNewLine();
	}
}
